package cn.zn.framework.common.aspect;

import cn.zn.framework.common.annotation.UniqueVer;
import cn.zn.framework.common.annotation.UniqueVerField;
import cn.zn.framework.common.utils.R;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.IService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 唯一验证切面
 *
 * @author zn
 * @since 2019-07-31
 */
@Aspect
@Component
@Slf4j
public class UniqueVerAspect implements ApplicationContextAware {

    ApplicationContext ac;

    @Pointcut("@annotation(cn.zn.framework.common.annotation.UniqueVer)")
    public void uniqueVerCut() {

    }

    @Around("uniqueVerCut()")
    public Object uniqueVer(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //请求的参数
        Object[] args = point.getArgs();
        UniqueVerField uniqueVerField = null;
        UniqueVer uniqueVer = method.getAnnotation(UniqueVer.class);
        QueryWrapper queryWrapper = Wrappers.query();
        if (uniqueVer.notEntity()) {
            uniqueVerField = uniqueVer.value().newInstance();
            String idKey = uniqueVer.idKey();
            String[] verKeys = uniqueVer.verKeys();
            //获取被拦截方法参数名列表(使用Spring支持类库)
            LocalVariableTableParameterNameDiscoverer u =
                    new LocalVariableTableParameterNameDiscoverer();
            String[] paraNameArr = u.getParameterNames(method);
            for (int i = 0; i < paraNameArr.length; i++) {
                if (paraNameArr[i].equals(idKey)) {
                    if (args[i] != null) {
                        queryWrapper.ne(idKey, args[i]);
                    }
                    continue;
                }
                for (String verKey : verKeys) {
                    if (paraNameArr[i].equals(verKey)) {
                        queryWrapper.eq(verKey, args[i]);
                    }
                }
            }
        } else {
            try {
                uniqueVerField = (UniqueVerField) args[0];
            } catch (Exception e) {
                return R.error(String.format("唯一性验证失败，%s方法的第一个参数不正确", point.getTarget().getClass().getName() + "." + signature.getName() + "()"));
            }
            if (uniqueVerField.verKeys().length != uniqueVerField.verValues().length) {
                return R.error("唯一性验证失败，待验证的参数字段及值个数不一致");
            }
            if (uniqueVerField.serviceClazz() == null) {
                return R.error("唯一性验证失败，未设置serviceClazz");
            }
            for (int i = 0; i < uniqueVerField.verKeys().length; i++) {
                queryWrapper.eq(uniqueVerField.verKeys()[i], uniqueVerField.verValues()[i]);
            }
            if (uniqueVerField.getId() != null) {
                queryWrapper.ne("id", uniqueVerField.getId());
            }
        }
        try {
            IService service = ac.getBean(uniqueVerField.serviceClazz());
            int count = service.count(queryWrapper);
            if (count > 0) {
                return R.error(uniqueVerField.errorTip() == null ? "已存在相同记录，请重新录入！" : uniqueVerField.errorTip());
            }
        } catch (Exception e) {
            log.error("唯一性效验失败", e);
            return R.error(e.getMessage());
        }
        Object result = point.proceed();
        return result;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.ac = applicationContext;
    }
}
